﻿#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using PdfCreative.Pdf.Writer;

namespace PdfCreative.Pdf.Graphics
{
    /// <summary>
    /// The Stencil class represents a repeatable set of graphics commands. Stencils can be used multiple times,
    /// across pages, within one PDF document. Stencils can be drawn on other stencils as well as on pages.
    /// </summary>
    public class Stencil : IResourceWriter
    {
        // Private fields
        // ==============
        #region Private fields
        private Rectangle _boundingBox;                 // The stencil's bounding box.
        private Graphics _graphics;                     // The stencil's graphics interface.
        private MemoryStream _stencilStream;            // The stencil's content stream.
        #endregion



        // Interface implementations
        // =========================
        #region IResourceWriter Members
        /// <summary>
        /// True if this resource is a custom resource. False otherwise.
        /// </summary>
        public bool IsCustomResource { get { return true; } }

        /// <summary>
        /// Retrieves the scope of this resource.
        /// </summary>
        public ResourceScope Scope
        {
            get { return ResourceScope.Document; }
        }

        /// <summary>
        /// Writes the resource of this stencil to the specified PDF writer.
        /// </summary>
        /// <param name="graphics">The graphics object to use as a context</param>
        /// <param name="writer">The writer to write to</param>
        /// <param name="resourceReference">The resource reference</param>
        /// <param name="scope">The scope if this instance of the resource.</param>
        public void WriteResource(
            Graphics graphics,
            PdfWriter writer, 
            PdfResourceReference resourceReference, 
            ScopedResource scope)
        {
            // We create object references for the stencil's resources.
            PdfObjectReference definition = resourceReference.ObjectReference;
            PdfObjectReference resourcesObject = writer.CreateObjectReference();

            // We can flush the PDF writer now; at this stage the stencil's contents are fixed.
            _graphics.Writer.Flush();

            // We open an XObject object, and write the stencil stream into it.
            writer.OpenObject(definition, "XObject");
            writer.WriteDictionaryEntry("Subtype", "/Form");
            writer.WriteDictionaryEntry("Formtype", "1");
            writer.WriteDictionaryEntry("BBox", _boundingBox );
            writer.WriteDictionaryEntry("Matrix", "[1 0 0 1 0 0]");
            writer.WriteDictionaryEntry("Resources", resourcesObject);
            writer.WriteDictionaryEntry("Name", String.Format("/{0}", resourceReference.Identifier));
            writer.WriteDictionaryEntry("Length", _stencilStream.Position);
            writer.WriteRawContent(">>\r\nstream\r\n");
            writer.WriteBuffer(_stencilStream.GetBuffer(), 0, (int)_stencilStream.Position);
            writer.WriteRawContent("endstream\r\nendobj\r\n");

            // We need to add the stencil's resources to a resources object.
            WriteStencilResources(writer, resourcesObject);

        } // WriteResource()

        #endregion



        // Private implementation
        // ======================
        #region Private implementation
        /// <summary>
        /// Writes the list of resources used by the stencil.
        /// </summary>
        /// <param name="writer">The PDF writer</param>
        /// <param name="resourcesObject">The resources object</param>
        private void WriteStencilResources(PdfWriter writer, PdfObjectReference resourcesObject)
        {
            writer.OpenObject(resourcesObject);
            writer.WriteDictionaryEntry("ProcSet", "[/PDF /Text /ImageC]");

            // We need to write each resource type separately so we need to maintain a dictionary of lists.
            Dictionary<string, List<PdfResourceReference>> resourceDictionary = 
                new Dictionary<string, List<PdfResourceReference>>();

            // Copy fonts, images etc. into their respective lists.
            foreach (PdfResourceReference resource in _graphics.GetTargetResources())
            {
                // Find the resource type for this resource. If there is no list for this type yet, we add it.
                string resourceType = resource.ResourceType;
                if (false == resourceDictionary.ContainsKey(resourceType))
                    resourceDictionary[resourceType] = new List<PdfResourceReference>();

                // Add the resource to the appropriate list.
                resourceDictionary[resourceType].Add(resource);
            }

            // for each of the different resource types we create a dictionary entry with the resource references as its
            // value. The references are a dictionary in themselves, with the resource identifiers acting as keys.
            foreach (KeyValuePair<string, List<PdfResourceReference>> pair in resourceDictionary)
            {
                if (pair.Value.Count > 0)
                {
                    writer.WriteRawContent(String.Format("\t/{0}\t<<", pair.Key));
                    foreach (PdfResourceReference resource in pair.Value)
                        writer.WriteRawContent(String.Format(" {0}", resource));
                    writer.WriteRawContent(">>");
                }
            }

            writer.CloseObject();

        } // WriteStencilResources()
        #endregion



        // Public properties
        // =================
        #region Public properties
        /// <summary>
        /// Retrieves the stencil's graphics interface.
        /// </summary>
        public Graphics Graphics
        {
            get { return _graphics; }
        }

        /// <summary>
        /// Retrieves the stencil's height.
        /// </summary>
        public float Height
        {
            get { return _boundingBox.Height; }
        }

        /// <summary>
        /// Retrieves the stencil's width.
        /// </summary>
        public float Width
        {
            get { return _boundingBox.Width; }
        }
        #endregion



        // Private implementation
        // ======================
        #region Private implementation
        /// <summary>
        /// Creates a new Graphics instance based on this stencil and the specified
        /// base graphics.
        /// </summary>
        /// <param name="baseGraphics">The graphics instance to base the stencil's
        /// graphics on.</param>
        /// <returns>The graphics instance</returns>
        public void CreateGraphics(Graphics baseGraphics)
        {
            System.Diagnostics.Debug.Assert(null != baseGraphics);

            // We create a new Writer that will write the PDF instructions for us.
            // We match the page's media box to the stencil's size, and open a content stream.
            // (we need to get rid of the objects that the PDF writer creates - we're only
            // interested in the content stream itself)
            PdfWriter writer = new PdfWriter(_stencilStream);
            writer.OpenPage(new PdfRectangle(0,0, _boundingBox.Width, _boundingBox.Height));
            writer.OpenContentStream();
            writer.Flush();
            _stencilStream.Seek(0, SeekOrigin.Begin);

            _graphics = new Graphics(writer, baseGraphics, GraphicsTarget.Stencil, _boundingBox);
        }
        #endregion



        // Public constructor
        // ==================
        #region Public constructor
        /// <summary>
        /// Constructs a new Stencil instance.
        /// </summary>
        /// <param name="width">The stencil's width</param>
        /// <param name="height">The stencil's height</param>
        /// <param name="graphics">The graphics instance that this stencil was created in</param>
        internal Stencil(float width, float height, Graphics graphics)
        {
            System.Diagnostics.Debug.Assert(null != graphics);

            _boundingBox = new Rectangle(0, 0, width, height);
            _stencilStream = new MemoryStream();

            CreateGraphics(graphics);
        }
        #endregion

    } // Stencil class
}
